﻿using System.Collections.Generic;
using LoungeRepo.Contracts.Core;
using LoungeRepo.Contracts.EntityCache;
using LoungeRepo.Contracts.EntityStore;
using LoungeRepo.Contracts.Normalizer;
using LoungeRepo.Contracts.Serialization;
using LoungeRepo.EntityCache;
using LoungeRepo.EntityStore;
using LoungeRepo.Normalizer;
using LoungeRepo.Serialization;

namespace LoungeRepo.Core
{
    public partial class LoungeRepository : ILoungeRepository
    {
        public const string DEFAULT_LOUNGE_REPOSITORY_ROOT_FOLDER_NAME = "LoungeRepository.Root";
        public const string DEFAULT_PARTITION_NAME = "~Default";

        private readonly IEntityNormalizer normalizer;
        private readonly INormalizedEntitySerializer serializer;
        private readonly IEntityStore filesys;
        private readonly IEntityCache cache;


        public LoungeRepository() : this(DEFAULT_LOUNGE_REPOSITORY_ROOT_FOLDER_NAME) {}
        public LoungeRepository(string rootFolderPath)
        {
            this.normalizer = new EntityNormalizer(OnResolveEntityReferenceRequest);
            this.serializer = new BinarySerializer();
            this.filesys = new FilesystemEntityStore(rootFolderPath);
            this.cache = new SecondLevelCache();
        }


        public void Store(object nonEntityRoot, string id) { Store(nonEntityRoot, id, DEFAULT_PARTITION_NAME); }
        public void Store(object nonEntityRoot, string id, string parition)
        {
            this.cache.Register(nonEntityRoot, id, parition);

            var normalizedEntities = this.normalizer.Normalize(nonEntityRoot, id, parition);
            PersistNormalizedEntities(normalizedEntities);
        }

        public void Store(object entityRoot)
        {
            ILoungeRepoEntityIdentity entityIdentity;
            if (this.normalizer.TryExtractEntityIdentity(entityRoot, out entityIdentity))
            {
                this.cache.Register(entityRoot, entityIdentity.Id, entityIdentity.Partition);

                var normalizedEntities = this.normalizer.Normalize(entityRoot);
                PersistNormalizedEntities(normalizedEntities);
            }
        }


        public T Load<T>(string id) { return Load<T>(id, DEFAULT_PARTITION_NAME); }
        public T Load<T>(string id, string partition)
        {
            object entity;
            if (this.cache.TryGetByKey(id, partition, out entity))
                return (T)entity;

            INormalizedEntity normalizedEntity = DepersistNormalizedEntity(id, partition);
            return (T)this.normalizer.Denormalize(normalizedEntity);
        }


        public void Delete(string id) { Delete(id, DEFAULT_PARTITION_NAME);  }
        public void Delete(string id, string partition)
        {
            this.cache.Evict(id, partition);
            this.filesys.Delete(id, partition);
        }


        public IEnumerable<T> GetExtent<T>()
        {
            return this.cache.GetByType<T>();
        }


        private object OnResolveEntityReferenceRequest(ILoungeRepoEntityIdentity entityReference)
        {
            object entity;
            if (this.cache.TryGetByKey(entityReference.Id, entityReference.Partition, out entity))
                return entity;

            return DepersistNormalizedEntity(entityReference.Id, entityReference.Partition);
        }


        #region Implementation of IDisposable
        public void Dispose()
        {}
        #endregion
    }
}
